import sys
import ast
import pysd
import os.path
from flask import Flask, json, jsonify
from flask import request
from flask import make_response

class InvalidUsage(Exception):
    status_code = 400

    def __init__(self, message, status_code=None, payload=None):
        Exception.__init__(self)
        self.message = message
        if status_code is not None:
            self.status_code = status_code
        self.payload = payload

    def to_dict(self):
        rv = dict(self.payload or ())
        rv['message'] = self.message
        return rv


app = Flask(__name__)

def replace_last(source_string, replace_what, replace_with):
    head, _sep, tail = source_string.rpartition(replace_what)
    return head + replace_with + tail

@app.route('/')
def test():
    return "Hello world!"


def get_param(key):
    return request.args.get(key, "")


@app.route('/test')
def test_method():
    params = get_param("params")
    ts_start = get_param("ts_start")
    ts_stop = get_param("ts_stop")
    return "success: " + "\tparams: " + params + "\tts_start: " + ts_start + "\tts_stop: " + ts_stop

def convert_results(results):
    return results.reset_index().to_json()


def convert_params(parameters):
    return ast.literal_eval(parameters)


def safety_call(delegate_function, *arguments):
    try:
        return delegate_function(*arguments)
    except AttributeError as ex:
        raise InvalidUsage('Attribute error: ' + str(ex), status_code=400)

    except KeyError as ex:
        raise InvalidUsage('Key error: ' + str(ex), status_code=400)

    except NameError as ex:
        raise InvalidUsage('Invalid model configuration error: ' + str(ex), status_code=400)

    except ValueError as ex:
        raise InvalidUsage('Invalid model configuration error: ' + str(ex), status_code=400)

    except TypeError as ex:
        raise InvalidUsage('Invalid model configuration error: ' + str(ex), status_code=400)

    except NotImplementedError as ex:
        raise InvalidUsage(str(ex), status_code=503)

    except Exception as ex:
        # Parse error
        if ex.pos and ex.text:
            raise InvalidUsage('Parse error at line "' + ex.text + '", position: ' + str(ex.pos), status_code=400)
        else:
            raise InvalidUsage(str(ex), status_code=400)

def run_model(model):
    parameters = get_param("params")
    ts_array = get_param("ts_array")
    ts_start = get_param("ts_start")
    ts_stop = get_param("ts_stop")
    parameters_defined = False
    timesteps_defined = False

    if parameters:
        parameters_defined = True
        parameters = "{" + parameters + "}"

    if ts_array:
        timesteps_defined = True
        timesteps = convert_params(ts_array)

    if ts_start and ts_stop:
        timesteps_defined = True
        timestep_start = int(ts_start)
        timestep_stop = int(ts_stop)

    if not parameters_defined and not timesteps_defined:
        print("empty")
        results = model.run()

    if parameters_defined and not timesteps_defined:
        print("parameters")
        results = model.run(params=convert_params(parameters))

    if not parameters_defined and timesteps_defined:
        print("timesteps")
        if ts_array:
            results = model.run(return_timestamps=timesteps)
        else:
            results = model.run(return_timestamps=range(timestep_start, timestep_stop))

    if parameters_defined and timesteps_defined:
        print("parameters & timesteps")
        if ts_array:
            results = model.run(params=convert_params(parameters), return_timestamps=timesteps)
        else:
            results = model.run(params=convert_params(parameters), return_timestamps=range(timestep_start, timestep_stop))

    return convert_results(results)

def load_and_run(mdlFormat):
    modelPath = get_param('modelPath')
    reload = get_param('reload')
    if mdlFormat:
        frmExtension = '.mdl'
    else:
        frmExtension = '.xmile'

    if reload or bool(reload):
        if mdlFormat:
            model = pysd.read_vensim(modelPath)
        else:
            model = pysd.read_xmile(modelPath)
    else:
        modelPy = replace_last(modelPath, frmExtension, '.py')
        if os.path.isfile(modelPy):
            model = pysd.load(modelPy)
        else:
            if mdlFormat:
                model = pysd.read_vensim(modelPath)
            else:
                model = pysd.read_xmile(modelPath)
    return run_model(model)

@app.route('/api/pysd/vensim/load', methods=['GET'])
def load_vensim_model_by_path():
    modelPath = get_param('modelPath')
    safety_call(pysd.read_vensim, modelPath)
    return "success"

@app.route('/api/pysd/vensim/run', methods=['GET'])
def run_vensim_model_by_path():
    return safety_call(load_and_run, True)

@app.route('/api/pysd/xmile/load', methods=['GET'])
def load_xmile_model_by_path():
    modelPath = get_param('modelPath')
    safety_call(pysd.read_xmile, modelPath)
    return "success"

@app.route('/api/pysd/xmile/run', methods=['GET'])
def run_xmile_model_by_path():
    return safety_call(load_and_run, False)

@app.errorhandler(InvalidUsage)
def handle_invalid_usage(error):
    response = jsonify(error.to_dict())
    response.status_code = error.status_code
    return response

portNumber = 8888
if len(sys.argv) >= 2:
    portNumber = int(sys.argv[1]);
if __name__ == '__main__':
    app.run(port=int(portNumber))
